package org.nlp2rdf.navigator.benchmark;

import com.google.common.collect.Sets;
import org.aksw.commons.util.random.RandomUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;


/**
 * Created by Claus Stadler
 * Date: Oct 15, 2010
 * Time: 4:57:29 PM
 *
 * A class which encapsulates the process of learning, classifying and retrieving new examples for a further iteration.
 * FIXME Make it "idiot"-safe so that methods must always be invoked in the order: next(), learn(), classify(), next()...
 *
 *
 */
public class PhaseSet<E, D> {
    private static final Logger logger = LoggerFactory.getLogger(PhaseSet.class);

    private IPickingStrategy<E> pickingStrategy;

    private Sample<E> all;
    private Sample<E> current;
    private Sample<E> pool;
    private Set<E> examples;
    private D learnedDescription;
    private Sample<E> nextAdditionalSamples = Sample.create();
    private IGenericLearner<E, D> learner;
    private IGenericClassifier<E, D> classifier;
    private long learnDuration;
    private long classifyDuration;

    private int phaseId = 0;


    /**
     * @param all        All positives and all negatives. all.negatives is ignored.
     * @param pool       The pool of positives and negatives from which can be drawn
     * @param init       The initial training sample
     * @param learner
     * @param classifier
     */
    public PhaseSet(Sample<E> all, Sample<E> pool, Sample<E> init, IGenericLearner<E, D> learner, IGenericClassifier<E, D> classifier, IPickingStrategy pickingStrategy) {
        this.pickingStrategy = pickingStrategy;

        this.all = all;
        this.pool = pool;
        this.current = Sample.create();
        this.nextAdditionalSamples = Sample.createCopy(init);
        this.learner = learner;
        this.classifier = classifier;
    }


    

    public Sample<E> getCurrent() {
        return current;
    }


    public int getPhaseId() {
        return phaseId;
    }

    public Sample<E> getNextSamples() {
        return Sample.createCopy(
                Sets.union(nextAdditionalSamples.getPositives(), current.getPositives()),
                Sets.union(nextAdditionalSamples.getNegatives(), current.getNegatives()));
    }

    public Set<E> getExamples() {
        return examples;
    }

    public D getLearnedDescription() {
        return learnedDescription;
    }

    /**
     * Attempts to advance. Returns false if there is no next iteration (there weren't any samples to learn from)
     *
     * @return
     */    
    public boolean next() {
        ++phaseId;
        if(nextAdditionalSamples.isEmpty()) {
            return false;
        }

        //logger.info("----------------------------");
        logger.trace("Entering phase " + phaseId);

        current.addAll(nextAdditionalSamples);
        nextAdditionalSamples.clear();


        return true;
    }


    /**
     * Learns some description based on the current state
     *
     * @return
     * @throws Exception
     */
    public void learn()
            throws Exception
    {


        //current.getPositives().addAll(nextPositives);
        //current.getNegatives().addAll(nextNegatives);


        //logger.info("All current positives: " + current.getPositives().size());
        //logger.info("All current negatives: " + current.getNegatives().size());
        //logger.info("Positives[" + phaseId + "]: (" + current.getPositives().size() + ") " + current.getPositives());
        //logger.info("Negatives[" + phaseId + "]: (" + current.getNegatives().size() + ") " + current.getNegatives());


        assert all.getPositives().containsAll(current.getPositives());
        //assert all.getNegatives().containsAll(current.getNegatives());
        assert Sets.intersection(current.getPositives(), current.getNegatives()).isEmpty();
        assert Sets.intersection(current.getNegatives(), current.getPositives()).isEmpty();

        long learnStart = System.nanoTime();
        learnedDescription = learner.learn(current);
        learnDuration = System.nanoTime() - learnStart;

        //if (learnedDescription == null)
            //return false;
    }


    /**
     * Retrieves sampels based on the current state
     *
     */
    public void classify()
        throws Exception
    {
        long classifyStart = System.nanoTime();
        examples = classifier.classify(learnedDescription);
        classifyDuration = System.nanoTime() - classifyStart;

        //if (examples == null)
            //return false;


        Set<E> poolTruePositives = Sets.intersection(pool.getPositives(), examples);
        Set<E> falsePositives = Sets.difference(examples, all.getPositives());

        Sample<E> nextCandidates = Sample.create(
                Sets.difference(poolTruePositives, current.getPositives()),
                Sets.difference(falsePositives, current.getNegatives()));


        nextAdditionalSamples = pickingStrategy.pick(nextCandidates);

        //return true;
    }
}